/// <reference path = "Action.ts" />
/// <reference path = "Decorator.ts" />
/// <reference path = "Composite.ts" />

namespace bt {
    const Decorators: {[name: string]: typeof Decorator} = {
        Inverter,
        Limiter,
        MaxTime,
        Repeater,
        RepeatUntilFailure,
        RepeatUntilSuccess
    };

    const Composites: {[name: string]: typeof Composite} = {
        MemPriority,
        MemSequence,
        Priority,
        Sequence
    }

    const Actions: {[name: string]: typeof Action} = {
        Error,
        Failer,
        Runner,
        Succeeder,
        Wait,
        Logger
    }

    export class BehaviorTree {
        id: string;
        title: string;
        description: string;
        properties: any;
        root: BaseNode;
        debug: any;
    
        constructor() {
            this.id          = UUID.v4();
            this.title       = 'The behavior tree';
            this.description = 'Default description';
            this.properties  = {};
            this.root        = null;
            this.debug       = null;
        }
    
        load(data: any, names?: any) {
            names = names || {};
            this.title       = data.title || this.title;
            this.description = data.description || this.description;
            this.properties  = data.properties || this.properties;
            let nodes: {[name: string]: BaseNode } = {};
            let id, spec, node;
            for (id in data.nodes) {
                spec = data.nodes[id];
                let Cls;
                if (spec.name in names) {
                    Cls = names[spec.name];
                } else if (spec.name in Decorators) {
                    Cls = Decorators[spec.name];
                } else if (spec.name in Composites) {
                    Cls = Composites[spec.name];
                } else if (spec.name in Actions) {
                    Cls = Actions[spec.name];
                } else {
                    // Invalid node name
                    throw new EvalError(`当前执行的节点不存在：${spec.name}`);
                }
                console.log('spec.name', spec.name)
                node = new Cls(spec.properties);
                node.id = spec.id || node.id;
                node.title = spec.title || node.title;
                node.description = spec.description || node.description;
                node.properties = spec.properties || node.properties;
                nodes[id] = node;
            }
            
            for (id in data.nodes) {
                spec = data.nodes[id];
                node = nodes[id];
                if (node.category === CategoryType.COMPOSITE && spec.children) {
                    for (var i=0; i<spec.children.length; i++) {
                        var cid = spec.children[i];
                        node.children.push(nodes[cid]);
                    }
                } else if (node.category === CategoryType.DECORATOR && spec.child) {
                    node.child = nodes[spec.child];
                }
            }
            this.root = nodes[data.root];
        }
    
    
        tick(target: any, blackboard: Blackboard) : ActionStatus {
            const tick = new Tick();
            tick.debug      = this.debug;
            tick.target     = target;
            tick.blackboard = blackboard;
            tick.tree       = this;
            let status = this.root._execute(tick);
            let lastOpenNodes = blackboard.get('openNodes', this.id);
            let currOpenNodes = tick._openNodes.slice(0)
            let start = 0;
            let i = 0;
            for (i = 0; i < Math.min(lastOpenNodes.length, currOpenNodes.length); i++) {
                start = i + 1;
                if (lastOpenNodes[i] !== currOpenNodes[i]) {
                    break;
                }
            }
            for (i = lastOpenNodes.length-1; i >= start; i--) {
                lastOpenNodes[i]._close(tick);
            }
            blackboard.set('openNodes', currOpenNodes, this.id);
            blackboard.set('nodeCount', tick._nodeCount, this.id);
            return status;
        }
    
        dump() {
            let data: any = {};
            let customNames = [];
            data.title        = this.title;
            data.description  = this.description;
            data.root         = (this.root)? this.root.id:null;
            data.properties   = this.properties;
            data.nodes        = {};
            data.custom_nodes = [];
            if (!this.root) {
                return data;
            } 
            let stack = [this.root];
            while (stack.length > 0) {
                let node = stack.pop();
                let spec: any = {};
                spec.id = node.id;
                spec.name = node.name;
                spec.title = node.title;
                spec.description = node.description;
                spec.properties = node.properties;
                spec.parameters = node.parameters;
            
                // verify custom node
                let proto = (node.constructor && node.constructor.prototype);
                let nodeName = (proto && proto.name) || node.name;
                if (!Decorators[nodeName] && !Composites[nodeName] && !Actions[nodeName] && customNames.indexOf(nodeName) < 0) {
                    let subdata: any = {};
                    subdata.name = nodeName;
                    subdata.title = (proto && proto.title) || node.title;
                    subdata.category = node.category;
            
                    customNames.push(nodeName);
                    data.custom_nodes.push(subdata);
                }
            
                // store children/child
                if (node.category === CategoryType.COMPOSITE && node.children) {
                    let children = [];
                    for (var i=node.children.length-1; i>=0; i--) {
                    children.push(node.children[i].id);
                    stack.push(node.children[i]);
                    }
                    spec.children = children;
                } else if (node.category === CategoryType.DECORATOR && node.child) {
                    stack.push(node.child);
                    spec.child = node.child.id;
                }
            
                data.nodes[node.id] = spec;
            }
        
            return data;        
        }
    }
}